#include "bsp_w25q128.h"

#include "main.h"
#include "spi.h"

#define W25Q128_PAGE_PROG       (0x02)
#define W25Q128_RD_DATA         (0x03)
#define W25Q128_RD_SR1          (0x05)
#define W25Q128_WR_ENABLE       (0x06)
#define W25Q128_ERASE_SECTOR    (0x20)
#define W25Q128_RD_ID           (0x90)

#define W25Q128_SPI             (hspi1)
#define W25Q128_ENABLE          HAL_GPIO_WritePin(W25Q128_CS_GPIO_Port, W25Q128_CS_Pin, GPIO_PIN_RESET)
#define W25Q128_DISABLE         HAL_GPIO_WritePin(W25Q128_CS_GPIO_Port, W25Q128_CS_Pin, GPIO_PIN_SET)

static void w25q128_wait_busy(void)
{
    uint8_t sr1 = 0xff;
    uint8_t cmd = W25Q128_RD_SR1;

    W25Q128_ENABLE;

    do{
        if (!HAL_SPI_Transmit(&W25Q128_SPI, &cmd, sizeof(cmd), 1000) &&
            !HAL_SPI_Receive(&W25Q128_SPI, &sr1, sizeof(sr1), 1000))
        {
            continue;
        }

    } while(sr1 & 0x01);

    W25Q128_DISABLE;
}

static void w25q128_wr_enable(void)
{
    uint8_t cmd = W25Q128_WR_ENABLE;

    W25Q128_ENABLE;

    HAL_StatusTypeDef return_status;

    return_status = HAL_SPI_Transmit(&W25Q128_SPI, &cmd, sizeof(cmd), 1000);
    assert_param(return_status == HAL_OK);

    W25Q128_DISABLE;
}

void w25q128_read_id(uint16_t *id)
{
    uint8_t sequence[4] = {W25Q128_RD_ID, 0x00, 0x00, 0x00};

    W25Q128_ENABLE;

    HAL_StatusTypeDef return_status;

    return_status = HAL_SPI_Transmit(&W25Q128_SPI, sequence, sizeof(sequence), 1000);
    assert_param(return_status == HAL_OK);

    return_status = HAL_SPI_Receive(&W25Q128_SPI, (uint8_t *)id, 2, 1000);
    assert_param(return_status == HAL_OK);

    W25Q128_DISABLE;
}


void w25q128_erase_sector(uint32_t addr)
{
    assert_param(addr >=0 && addr <=0xffffff);

    uint8_t sequence[4];
    sequence[0] = W25Q128_ERASE_SECTOR;
    sequence[1] = (addr >> 16) & 0xff;
    sequence[2] = (addr >> 8) & 0xff;
    sequence[3] = addr & 0xff;

    w25q128_wr_enable();

    w25q128_wait_busy();

    W25Q128_ENABLE;

    HAL_StatusTypeDef return_status;
    return_status = HAL_SPI_Transmit(&W25Q128_SPI, sequence, sizeof(sequence), 1000);
    assert_param(return_status == HAL_OK);

    W25Q128_DISABLE;

    w25q128_wait_busy();
}

void w25q128_page_program(uint32_t addr, const uint8_t *data, uint16_t len)
{
    assert_param(addr >=0 && addr <=0xffffff);
    assert_param(len <= 256);

    uint8_t sequence[4];
    sequence[0] = W25Q128_PAGE_PROG;
    sequence[1] = (addr >> 16) & 0xff;
    sequence[2] = (addr >> 8) & 0xff;
    sequence[3] = addr & 0xff;

    w25q128_wr_enable();

    W25Q128_ENABLE;

    HAL_StatusTypeDef return_status;
    return_status = HAL_SPI_Transmit(&W25Q128_SPI, sequence, sizeof(sequence), 1000);
    assert_param(return_status == HAL_OK);

    return_status = HAL_SPI_Transmit(&W25Q128_SPI, data, len, 1000);
    assert_param(return_status == HAL_OK);

    W25Q128_DISABLE;

    w25q128_wait_busy();
}

void w25q128_read_data(uint32_t addr, uint8_t *data, uint16_t len)
{
    assert_param(addr >=0 && addr <=0xffffff);

    uint8_t sequence[4];
    sequence[0] = W25Q128_RD_DATA;
    sequence[1] = (addr >> 16) & 0xff;
    sequence[2] = (addr >> 8) & 0xff;
    sequence[3] = addr & 0xff;

    W25Q128_ENABLE;

    HAL_StatusTypeDef return_status;
    return_status = HAL_SPI_Transmit(&W25Q128_SPI, sequence, sizeof(sequence), 1000);
    assert_param(return_status == HAL_OK);

    return_status = HAL_SPI_Receive(&W25Q128_SPI, data, len, 1000);
    assert_param(return_status == HAL_OK);

    W25Q128_DISABLE;
}
